#include "argsFileParser.h"

#include <iostream>
#include <fstream>
#include <string.h>



ArgsFileEntry::ArgsFileEntry(void) {}


ArgsFileEntry::~ArgsFileEntry(void)
{
    //std::cout << " Dying: " << type << ":" << name << std::endl;
}


ArgsFileValueEntry::ArgsFileValueEntry(void) {}


ArgsFileValueEntry::~ArgsFileValueEntry(void) {}


ArgsFileGroupEntry::ArgsFileGroupEntry(void):
    closed(false)
{}


ArgsFileGroupEntry::~ArgsFileGroupEntry(void)
{
    std::vector<ArgsFileEntry *>::iterator it;

    for(it = children.begin(); it < children.end(); ++it)
    {
        delete *it;
    }
}


ArgsFile::ArgsFile(void) {}


ArgsFile::~ArgsFile(void) {}

void ArgsFile::error(std::string msg)
{
    std::cerr << "ArgsFile Error: " << msg << std::endl;
}


ArgsFile *ArgsFile::parse(std::string filePath)
{
    std::string xml;
    std::string line;
    std::ifstream fin(filePath.c_str());

    if (fin.fail())
    {
        error("reading " + filePath);
        return NULL;
    }

    // Try to read the file
    fin.seekg(0, std::ios::end);
    size_t length = fin.tellg();
    fin.seekg(0, std::ios::beg);
    char* buffer = new char[length + 1];
    fin.read(buffer, length);
    buffer[length] = '\0';
    fin.close();

    // Parse the xml text
    return parseXML(buffer, filePath);
}


std::string ArgsFile::extractExtraAttr( ArgsFileExtraAttrs &extraAttrs,
                                        std::string attrName)
{
    std::map<std::string, std::string>::iterator it;
    std::map<std::string, std::string>::iterator toDelete = extraAttrs.end();

    std::string attrValue = "";

    for( it = extraAttrs.begin(); it != extraAttrs.end(); ++it )
    {
        if(it->first == attrName)
        {
            attrValue = it->second;
            toDelete = it;
        }
    }

    // Remove the attrName entry in extraAttrs if it exists`
    if(toDelete != extraAttrs.end())
    {
        extraAttrs.erase(toDelete);
    }

    return attrValue;
}


ArgsFile *ArgsFile::parseXML(char *buffer, std::string filePath)
{
    xml_document<> doc;
    doc.parse<0>(buffer);

    // we don't need the xml text buffer any more
    delete [] buffer;

    xml_node<>* node = doc.first_node();

    ArgsFile *argsFile = parseRootNode(node);
    if(argsFile == NULL)
    {
        return NULL;
    }

    argsFile->filePath = filePath;
    argsFile->type = "file";

    return argsFile;
}


ArgsFile *ArgsFile::parseRootNode(xml_node<>* rootNode)
{
    std::string format = "1.0";

    for (xml_attribute<> *attr = rootNode->first_attribute();
         attr; attr = attr->next_attribute())
    {
        if(strcmp(attr->name(), "format") == 0)
        {
            format = std::string(attr->value());
        }
    }

    if(format != "1.0")
    {
        error("format not supported:" + format);
        return NULL;
    }

    // Parse children
    std::vector<ArgsFileEntry *> children;

    for (xml_node<> *node = rootNode->first_node();
         node; node = node->next_sibling())
    {
        ArgsFileEntry *child = parseEntry(node);

        if(child != NULL)
        {
            children.push_back(child);
        }
    }

    ArgsFile *argsFile = new ArgsFile();
    argsFile->format = format;
    argsFile->children = children;

    return argsFile;
}


ArgsFileEntry *ArgsFile::parseEntry(xml_node<>* parentNode)
{
    // get the type of entry
    std::string type = std::string(parentNode->name());

    //Extra values and entry name
    std::map<std::string, std::string> extraAttrs;
    std::string name = "";

    for ( xml_attribute<> *attr = parentNode->first_attribute();
          attr; attr = attr->next_attribute() )
    {
        if(strcmp(attr->name(), "name") == 0)
        {
            name = std::string(attr->value());
        }
        else
        {
            extraAttrs[attr->name()] = std::string(attr->value());
        }
    }

    if( name == "" )
    {
        error("name not set for tag " + type);
        return NULL;
    }


    // Check the type and create the appropriate entry
    ArgsFileEntry *entry = NULL;

    if( type == "group" || type == "page")
    {
        entry = parseGroup(parentNode, extraAttrs);
    }
    else if( type == "param" )
    {
        entry = parseParam(parentNode, extraAttrs);

        //TODO: get widget, default, hintsdict, etc..
    }

    if(entry != NULL)
    {
        entry->name = name;
        entry->type = type;
        entry->extraAttrs = extraAttrs;
    }

    return entry;
}


ArgsFileGroupEntry *ArgsFile::parseGroup( xml_node<>* parentNode,
                                          ArgsFileExtraAttrs &extraAttrs)
{
    // closed
    std::string closedStr = extractExtraAttr(extraAttrs, "closed");
    bool closed = (closedStr == "True");


    // Iterate through the children
    std::vector<ArgsFileEntry *> children;

    for ( xml_node<> *node = parentNode->first_node();
          node; node = node->next_sibling() )
    {
        ArgsFileEntry *child = parseEntry(node);

        if(child != NULL)
        {
            children.push_back(child);
        }
    }

    ArgsFileGroupEntry *groupEntry = new ArgsFileGroupEntry();
    groupEntry->children = children;
    groupEntry->closed = closed;

    return groupEntry;
}


ArgsFileValueEntry *ArgsFile::parseParam( xml_node<>* node,
                                          ArgsFileExtraAttrs &extraAttrs)
{

    // widget
    std::string widget = extractExtraAttr(extraAttrs, "widget");

    // hintsdict
    for (xml_node<> *childNode = node->first_node();
         childNode; childNode = childNode->next_sibling())
    {
        std::string type = std::string(node->name());

        if(type != "hintdict")
        {
            error("Child of a param must be hintdict");
            return NULL;
        }

        //TODO
    }

    ArgsFileValueEntry *param = new ArgsFileValueEntry();
    param->widget = widget;

    return param;
}




